import * as THREE from '../../build/three.module.js';
import Stats from '../../jsm/libs/stats.module.js';
import { Curves } from '../../jsm/curves/CurveExtras.js';
import { OrbitControls } from '../../jsm/controls/OrbitControls.js';
import { GUI } from '../../jsm/libs/dat.gui.module.js';

/**
 * 构建样条曲线列表
 */
const pipeSpline = new THREE.CatmullRomCurve3([
  new THREE.Vector3( 0, 10, - 10 ), new THREE.Vector3( 10, 0, - 10 ),
  new THREE.Vector3( 20, 0, 0 ), new THREE.Vector3( 30, 0, 10 ),
  new THREE.Vector3( 30, 0, 20 ), new THREE.Vector3( 20, 0, 30 ),
  new THREE.Vector3( 10, 0, 30 ), new THREE.Vector3( 0, 0, 30 ),
  new THREE.Vector3( - 10, 10, 30 ), new THREE.Vector3( - 10, 20, 30 ),
  new THREE.Vector3( 0, 30, 30 ), new THREE.Vector3( 10, 30, 30 ),
  new THREE.Vector3( 20, 30, 15 ), new THREE.Vector3( 10, 30, 10 ),
  new THREE.Vector3( 0, 30, 10 ), new THREE.Vector3( - 10, 20, 10 ),
  new THREE.Vector3( - 10, 10, 10 ), new THREE.Vector3( 0, 0, 10 ),
  new THREE.Vector3( 10, - 10, 10 ), new THREE.Vector3( 20, - 15, 10 ),
  new THREE.Vector3( 30, - 15, 10 ), new THREE.Vector3( 40, - 15, 10 ),
  new THREE.Vector3( 50, - 15, 10 ), new THREE.Vector3( 60, 0, 10 ),
  new THREE.Vector3( 70, 0, 0 ), new THREE.Vector3( 80, 0, 0 ),
  new THREE.Vector3( 90, 0, 0 ), new THREE.Vector3( 100, 0, 0 )
]);
const sampleClosedSpline = new THREE.CatmullRomCurve3([
  new THREE.Vector3( 0, - 40, - 40 ),
  new THREE.Vector3( 0, 40, - 40 ),
  new THREE.Vector3( 0, 140, - 40 ),
  new THREE.Vector3( 0, 40, 40 ),
  new THREE.Vector3( 0, - 40, 40 )
]);
sampleClosedSpline.curveType = 'catmullrom';
sampleClosedSpline.closed = true;
// 样条曲线列表
const splines = {
  GrannyKnot: new Curves.GrannyKnot(),
  HeartCurve: new Curves.HeartCurve( 3.5 ),
  VivianiCurve: new Curves.VivianiCurve( 70 ),
  KnotCurve: new Curves.KnotCurve(),
  HelixCurve: new Curves.HelixCurve(),
  TrefoilKnot: new Curves.TrefoilKnot(),
  TorusKnot: new Curves.TorusKnot( 20 ),
  CinquefoilKnot: new Curves.CinquefoilKnot( 20 ),
  TrefoilPolynomialKnot: new Curves.TrefoilPolynomialKnot( 14 ),
  FigureEightPolynomialKnot: new Curves.FigureEightPolynomialKnot(),
  DecoratedTorusKnot4a: new Curves.DecoratedTorusKnot4a(),
  DecoratedTorusKnot4b: new Curves.DecoratedTorusKnot4b(),
  DecoratedTorusKnot5a: new Curves.DecoratedTorusKnot5a(),
  DecoratedTorusKnot5c: new Curves.DecoratedTorusKnot5c(),
  PipeSpline: pipeSpline,
  SampleClosedSpline: sampleClosedSpline
}

// dat 参数对象
const param = {
  spline: 'GrannyKnot',
  scale: 2,
  extrusionSegments: 50,
  radiusSegments: 3,
  closed: true,
  animationView: false,
  lookAhead: false,
  cameraHelper: false
}

// 场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);

// 容器
const parent = new THREE.Object3D();
scene.add(parent);

// 打光
const light = new THREE.DirectionalLight(0xffffff);
light.position.set(0, 0, 1);
scene.add(light);

// 主相机
const camera = new THREE.PerspectiveCamera(50, innerWidth / innerHeight, 1, 1000);
camera.position.set(0, 50, 500);

// 动画相机
const splineCamera = new THREE.PerspectiveCamera(85, innerWidth / innerHeight, 1, 1000);
parent.add(splineCamera);

// 相机辅助线
const cameraHelper = new THREE.CameraHelper(splineCamera);
scene.add(cameraHelper);
cameraHelper.visiable = param.cameraHelper;

// 动画相机示意
const cameraEye = new THREE.Mesh(new THREE.SphereBufferGeometry(5), new THREE.MeshBasicMaterial({ color: 0xdddddd }));
parent.add(cameraEye);
cameraEye.visiable = param.cameraHelper;

// 材质
const material = new THREE.MeshLambertMaterial({ color: 0xff00ff });
const wireFrameMaterial = new THREE.MeshBasicMaterial({
  color: 0x000000,
  opacity: .3,
  wireframe: true,
  transparent: true
})

let mesh;
// 添加几何体
const addGeometry = (geo) => {
  mesh = new THREE.Mesh(geo, material);
  const wireframeMesh = new THREE.Mesh(geo, wireFrameMaterial);
  mesh.add(wireframeMesh);

  parent.add(mesh);
}

// 设置缩放
const setScale = () => {
  mesh.scale.set(param.scale, param.scale, param.scale);
}

let tubeGeometry;
const addTube = () => {
  if (mesh !== undefined) {
    parent.remove(mesh);
    mesh.geometry.dispose();
  }
  const extrudePath = splines[param.spline];
  tubeGeometry = new THREE.TubeGeometry(extrudePath, param.extrusionSegments, 2, param.radiusSegments, param.closed);

  addGeometry(tubeGeometry);
  setScale();
}
addTube();

const animateCamera = () => {
  cameraEye.visible = param.cameraHelper;
  cameraHelper.visible = param.cameraHelper;
}

const renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(devicePixelRatio);
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);

const stats = new Stats();
document.body.appendChild(stats.dom);

/**
 * dat.GUI 构建
 */
const gui = new GUI({ width: 300 });
const folderGeometry = gui.addFolder('Geometry');
folderGeometry.add(param, 'spline', Object.keys(splines)).onChange(() => {
  addTube();
})
folderGeometry.add(param, 'scale', 2, 10).step(2).onChange(() => {
  setScale();
})
folderGeometry.add(param, 'extrusionSegments', 50, 500).step(50).onChange(() => {
  addTube();
})
folderGeometry.add(param, 'radiusSegments', 3, 12).step(1).onChange(() => {
  addTube();
})
folderGeometry.add(param, 'closed').onChange(() => {
  addTube();
})
folderGeometry.open();
const folderCamera = gui.addFolder('Camera');
folderCamera.add(param, 'animationView').onChange(() => {
  animateCamera();
})
folderCamera.add(param, 'lookAhead').onChange(() => {
  animateCamera();
})
folderCamera.add(param, 'cameraHelper').onChange(() => {
  animateCamera();
})
folderCamera.open();

// 控制器
new OrbitControls(camera, renderer.domElement);

console.log(tubeGeometry);

const binormal = new THREE.Vector3(); // 副法线，描述运动方向
const normal = new THREE.Vector3(); // 法线
const render = () => {
  // 让动画相机沿着样条曲线运动
  const time = Date.now();
  const looptime = 20 * 1000;
  const t = (time % looptime) / looptime;

  const pos = tubeGeometry.parameters.path.getPointAt( t );
  pos.multiplyScalar(param.scale);

  // 插值
  const segments = tubeGeometry.tangents.length;
  const pickt = t * segments;
  const pick = Math.floor(pickt);
  const pickNext = (pick + 1) % segments;

  binormal.subVectors(tubeGeometry.binormals[pickNext], tubeGeometry.binormals[pick]);
  binormal.multiplyScalar(pickt - pick).add(tubeGeometry.binormals[pick])

  const dir = tubeGeometry.parameters.path.getTangentAt(t);
  const offset = 15;

  normal.copy(binormal).cross(dir);

  // 让动画相机在与副法线距离一个偏移量的位置上运动
  pos.add(normal.clone().multiplyScalar(offset));

  splineCamera.position.copy(pos);
  cameraEye.position.copy(pos);

  // 向前时使用弧长来稳定化
  const lookAt = tubeGeometry.parameters.path.getPointAt((t + 30 / tubeGeometry.parameters.path.getLength() ) % 1).multiplyScalar(param.scale);

  if (!param.lookAhead) {
    lookAt.copy(pos).add(dir);
  }
  splineCamera.matrix.lookAt(splineCamera.position, lookAt, normal);
  splineCamera.quaternion.setFromRotationMatrix(splineCamera.matrix);

  cameraHelper.update();
  
  renderer.render(scene, param.animationView ? splineCamera : camera);
}

const animate = () => {
  requestAnimationFrame(animate);

  stats.update();
  render();
}
animate();